/******************************************************************************
 * * hal_ble_fmp.c - ble profile for find me
 *
 * *(C) Copyright 2021 Asr International Ltd.
 * * All Rights Reserved
 * ******************************************************************************/

/*********************
 *      INCLUDES
 *********************/

#include "hal_ble.h"
#include "hal_ble_profile.h"

#if USE_LV_BLE_DEMO != 0

/*********************
 *      DEFINES
 *********************/

/**********************
 *      TYPEDEFS
 **********************/
enum {
    BLE_IMME_ALERT_LEVEL_NO = 0,
    BLE_IMME_ALERT_LEVEL_MID,
    BLE_IMME_ALERT_LEVEL_HIGH,
};
typedef uint8_t BLE_IMME_ALERT_LEVEL_T;

typedef struct {
    ble_fmp_func_cb create_func;
    ble_fmp_func_cb destroy_func;
    struct le_uuid_16 uuid_immediate;
    struct le_uuid_16 uuid_alert;
    struct gatt_chrc chrc_alert;
} ble_fmp_service_global_t;

/**********************
 *  STATIC PROTOTYPES
 **********************/
static uint32_t ble_fmp_get_service(struct gatt_attr * attrs);
static void ble_fmp_deinit(void);
static void ble_fmp_client_process(bool on_off);
static int32_t ble_fmp_att_event_handle(struct bt_task_event * msg);
static int32_t ble_fmp_alert_write_cb(void * param);

/**********************
 *  STATIC VARIABLES
 **********************/
static ble_fmp_service_global_t * fmp_global = NULL;
static BLE_IMME_ALERT_LEVEL_T alert_level = BLE_IMME_ALERT_LEVEL_NO;

/**********************
 *      MACROS
 **********************/

/**********************
 *   GLOBAL FUNCTIONS
 ***********************/
void ble_fmp_setting(bool on_off, ble_fmp_func_cb create_cb, ble_fmp_func_cb destroy_cb)
{
    printf("%s: on_off %d, fmp_global %p\n", __FUNCTION__, on_off, fmp_global);

    if(fmp_global && !on_off) {
        // disable find me service
        Hal_Mem_Free(fmp_global);
        fmp_global = NULL;
        if(BLE_STATE_NULL != ble_get_state()) {
            ble_del_serv();
        }
    } else if(!fmp_global && on_off) {
        fmp_global = Hal_Mem_Alloc(sizeof(ble_fmp_service_global_t));
        Hal_Mem_Set(fmp_global, 0, sizeof(ble_fmp_service_global_t));

        fmp_global->create_func = create_cb;
        fmp_global->destroy_func = destroy_cb;

        // immediate alert service
        fmp_global->uuid_immediate.uuid.type = LE_UUID_TYPE_16;
        fmp_global->uuid_immediate.value = LE_ATT_UUID_IMMEDIATE_ALERT;
        fmp_global->uuid_alert.uuid.type = LE_UUID_TYPE_16;
        fmp_global->uuid_alert.value = LE_ATT_UUID_ALERT_LEVEL;
        fmp_global->chrc_alert.uuid = LE_ATT_UUID_ALERT_LEVEL;
        fmp_global->chrc_alert.properties = LE_ATT_CHARC_PROP_WWP;

        if(BLE_STATE_NULL != ble_get_state()) {
            ble_add_serv();
        }
    }
}

void ble_fmp_init(void)
{
    ble_profile_cb_reg(BLE_PROFILE_FIND_ME, ble_fmp_get_service,
                       ble_fmp_att_event_handle, ble_fmp_deinit);
}

void ble_fmp_client(bool on_off)
{
    BLE_STATE_T ble_state = ble_get_state();

    printf("%s: on_off %d, fmp_global %p, ble_state %d\n",
           __FUNCTION__, on_off, fmp_global, ble_state);

    if(BLE_STATE_NULL == ble_state) {
        printf("%s: warning, ble is not initiated\n", __FUNCTION__);
        return;
    }

    if(on_off) {
        ble_conn_est(BLE_PROFILE_FIND_ME);
    } else {
        ble_fmp_client_process(false);
    }
}

void ble_fmp_service_stop(void)
{
    printf("%s\n", __FUNCTION__);
    alert_level = BLE_IMME_ALERT_LEVEL_NO;
}

/**********************
 *   STATIC FUNCTIONS
 **********************/
static uint32_t ble_fmp_get_service(struct gatt_attr * attrs)
{
    if(!fmp_global) {
        printf("%s: imme alert service for fmp is not initiated\n", __FUNCTION__);
        return 0;
    }

    printf("%s\n", __FUNCTION__);

    struct gatt_attr serv_attrs[] = {
        LE_DECLARE_PRIMARY_SERVICE(fmp_global->uuid_immediate, LE_UUID_TYPE_16),
        LE_DECLARE_CHARACTERISTIC(fmp_global->chrc_alert, fmp_global->uuid_alert,
                                  LE_ATT_PM_WRITEABLE, ble_fmp_alert_write_cb, NULL, NULL, 0),
    };

    uint32_t size = sizeof(serv_attrs);
    printf("%s: size %d, attrs %p\n", __FUNCTION__, size, attrs);
    if(attrs) {
        Hal_Mem_Copy(attrs, serv_attrs, size);
    }

    return size;
}

static void ble_fmp_deinit(void)
{
    printf("%s\n", __FUNCTION__);

    alert_level = BLE_IMME_ALERT_LEVEL_NO;
}

static void ble_fmp_client_process(bool on_off)
{
    if(false == ble_is_profile_process(BLE_PROFILE_FIND_ME)) {
        return;
    }

    if(BLE_STATE_CONNECTED != ble_get_state()) {
        printf("%s: not in connected state\n", __FUNCTION__);
        return;
    }

    lv_ll_t * record = ble_get_service_record();
    if(!record) {
        printf("%s: no service record\n", __FUNCTION__);
        ble_conn_disc(BLE_PROFILE_FIND_ME);
        return;
    }

    ble_characteristic_record_t * characteristic = NULL;
    ble_service_record_t * service = _lv_ll_get_head(record);
    while(service) {
        if((LE_UUID_TYPE_16 == service->uuid.type)
           && (LE_ATT_UUID_IMMEDIATE_ALERT == service->uuid.u.uuid_16.value)) {
            characteristic = _lv_ll_get_head(&service->characteristic);
            while(characteristic) {
                if((LE_UUID_TYPE_16 == characteristic->uuid.type)
                   && (LE_ATT_UUID_ALERT_LEVEL == characteristic->uuid.u.uuid_16.value)) {
                    if(on_off) {
                        alert_level = BLE_IMME_ALERT_LEVEL_MID;
                    } else {
                        alert_level = BLE_IMME_ALERT_LEVEL_NO;
                    }
                    appbt_le_write(BTTASK_CMD_LE_WRITE_COMMAND, characteristic->value_handle,
                                   &alert_level, 1);
                    printf("%s: change alert_level to %d, handle %d, value_handle %d\n",
                           __FUNCTION__,alert_level, characteristic->handle,
                           characteristic->value_handle);
                    break;
                }
                characteristic = _lv_ll_get_next(&service->characteristic, characteristic);
            }
            break;
        }
        service = _lv_ll_get_next(record, service);
    }
    if(!characteristic) {
        printf("%s: no alert level characteristic for IAS\n", __FUNCTION__);
        ble_conn_disc(BLE_PROFILE_FIND_ME);
        return;
    }

    if(!on_off) {
        ble_conn_disc(BLE_PROFILE_FIND_ME);
    }
}

static int32_t ble_fmp_att_event_handle(struct bt_task_event * msg)
{
    switch(msg->event_id) {
    case BTTASK_IND_LE_GATT_CONNECTED:
        ble_fmp_client_process(true);
        break;

    default:
        break;
    }

    return 0;
}

static int32_t ble_fmp_alert_write_cb(void * param)
{
    struct gatt_attr_rw_parameters * rw = (struct gatt_attr_rw_parameters *)param;

    printf("%s: handle %04x, length %d, offset %d\n",
           __FUNCTION__, rw->handle, rw->length, rw->offset);
    for(uint16_t i = 0; i < rw->length; i++) {
        printf("%s: %d(%02x)\n", __FUNCTION__, rw->data[i], rw->data[i]);
    }

    if((1 < rw->length) || (BLE_IMME_ALERT_LEVEL_HIGH < rw->data[0])) {
        return LE_ERR_INVALID_ATTRIBUTE_VALUE;
    }

    // update to local memory
    if(rw->data[0] != alert_level) {
        printf("%s: alart level is changed from %d to %d\n",
               __FUNCTION__, alert_level, rw->data[0]);
        if((0 < rw->data[0]) && (0 < alert_level)) {
            return LE_ERR_OK;
        }

        if(rw->data[0]) {
            if(fmp_global->create_func) {
                printf("ble_create_func has been called in %s\n", __FUNCTION__);
                fmp_global->create_func();
            }
        } else {
            if(fmp_global->destroy_func) {
                printf("ble_destroy_func has been called in %s\n", __FUNCTION__);
                fmp_global->destroy_func();
            }
        }
        alert_level = rw->data[0];
    }
    return LE_ERR_OK;
}

#endif
